
local alien = require 'alien'
local bit = require 'bit'
local config = require 'config'
local error, print
    = error, print
local strbyte
    = string.byte


module (...)


local lib = alien.load(config.freetype6_path)

-- Basic types

local byte = 'char'
local ptr = 'pointer'
local Char = 'byte'
local Int = 'int'
local UInt = 'uint'
local Int16 = 'short'
local UInt16 = 'ushort'
local Int32 = 'int'
local UInt32 = 'uint'
local Short = 'short'
local UShort = 'ushort'
local Long = 'long'
local ULong = 'ulong'
local Bool = 'char'
-- Offset = size_t ??
-- PtrDist = ptrdiff_t ??
local String = 'string'
local Tag = 'uint'
local Error = 'int'
local Fixed = 'long'
local Pointer = 'pointer'
local Pos = 'long'
local FWord = 'short'
local UFWord = 'ushort'
local F2Dot14 = 'short'
local F26Dot6 = 'long'

-- Constants / Enums

local Pixel_Mode = 'int'
PIXEL_MODE_NONE = 0
PIXEL_MODE_MONO = 1
PIXEL_MODE_GRAY = 2
PIXEL_MODE_GRAY2 = 3
PIXEL_MODE_GRAY4 = 4
PIXEL_MODE_LCD = 5
PIXEL_MODE_LCD_V = 6

--[[

-- deprecated
local Palette_Mode = 'int'
palette_mode_rgb = 0
palette_mode_rgba = 1

--]]

function IMAGE_TAG(_x1, _x2, _x3, _x4)
  return bit.bor(bit.lshift(strbyte(_x1), 24), bit.lshift(strbyte(_x2), 16), bit.lshift(strbyte(_x3), 8), strbyte(_x4))
end

local Glyph_Format = 'int'
GLYPH_FORMAT_NONE = IMAGE_TAG('\0', '\0', '\0', '\0')
GLYPH_FORMAT_COMPOSITE = IMAGE_TAG('c', 'o', 'm', 'p')
GLYPH_FORMAT_BITMAP = IMAGE_TAG('b', 'i', 't', 's')
GLYPH_FORMAT_OUTLINE = IMAGE_TAG('o', 'u', 't', 'l')
GLYPH_FORMAT_PLOTTER = IMAGE_TAG('p', 'l', 'o', 't')

local Size_Request_Type = 'int'
SIZE_REQUEST_TYPE_NOMINAL = 0
SIZE_REQUEST_TYPE_REAL_DIM = 1
SIZE_REQUEST_TYPE_BBOX = 2
SIZE_REQUEST_TYPE_CELL = 3
SIZE_REQUEST_TYPE_SCALES = 4

local Render_Mode = 'int'
RENDER_MODE_NORMAL = 0
RENDER_MODE_LIGHT = 1
RENDER_MODE_MONO = 2
RENDER_MODE_LCD = 3
RENDER_MODE_LCD_V = 4

local Kerning_Mode = 'int'
KERNING_DEFAULT = 0
KERNING_UNFITTED = 1
KERNING_UNSCALED = 2

OPEN_MEMORY = 0x1
OPEN_STREAM = 0x2
OPEN_PATHNAME = 0x4
OPEN_DRIVER = 0x8
OPEN_PARAMS = 0x10

LOAD_DEFAULT = 0x0
LOAD_NO_SCALE = 0x1
LOAD_NO_HINTING = 0x2
LOAD_RENDER = 0x4
LOAD_NO_BITMAP = 0x8
LOAD_VERTICAL_LAYOUT = 0x10
LOAD_FORCE_AUTOHINT = 0x20
LOAD_CROP_BITMAP = 0x40
LOAD_PEDANTIC = 0x80
LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH = 0x200
LOAD_NO_RECURSE = 0x400
LOAD_IGNORE_TRANSFORM = 0x800
LOAD_MONOCHROME = 0x1000
LOAD_LINEAR_DESIGN = 0x2000
LOAD_NO_AUTOHINT = 0x8000

-- Structures

Open_Args = alien.defstruct{
  {'flags', Uint};
  {'memory_base', ptr --[[Byte*]]};
  {'memory_size', Long};
  {'string', String};
  {'stream', ptr --[[Stream]]};
  {'driver', ptr --[[Module]]};
  {'num_params', Int};
  {'params', ptr --[[Parameter*]]};
}

Parameter = alien.defstruct{
  {'tag', ULong};
  {'data', Pointer};
}

Vector = alien.defstruct{
  {'x', Long};
  {'y', Long};
}

FaceRec = alien.defstruct{
  {'num_faces', Long};
  {'face_index', Long};
  
  {'face_flags', Long};
  {'style_flags', Long};
  
  {'num_glyphs', Long};
  
  {'family_name', String};
  {'style_name', String};
  
  {'num_fixed_sizes', Int};
  {'available_sizes', ptr --[[Bitmap_Size*]]};
  
  {'num_charmaps', Int};
  {'charmaps', ptr --[[CharMap*]]};
  
  {'generic_data', ptr --[[void*]]};
  {'generic_finalizer', 'callback' --[[Generic_Finalizer]]};

  --[[
   The following member variables (down to `underline_thickness')
   are only relevant to scalable outlines; cf. @Bitmap_Size
   for bitmap fonts.                                             
  --]]
  {'bbox_xMin', Long};
  {'bbox_yMin', Long};
  {'bbox_xMax', Long};
  {'bbox_yMax', Long};
  
  {'units_per_EM', UShort};
  {'ascender', Short};
  {'descender', Short};
  {'height', Short};
  
  {'max_advance_width', Short};
  {'max_advance_height', Short};
  
  {'underline_position', Short};
  {'underline_thickness', Short};
  
  {'glyph', ptr --[[GlyphSlot]]};
  {'size', ptr --[[Size]]};
  {'charmap', ptr --[[CharMap]]};

  -- private

  {'driver', ptr --[[Driver]]};
  {'memory', ptr --[[Memory]]};
  {'stream', ptr --[[Stream]]};
  
  {'sizes_list_head', ptr --[[ListNode]]};
  {'sizes_list_tail', ptr --[[ListNode]]};
  
  {'autohint_data', ptr --[[void*]]};
  {'autohint_finalizer', 'callback' --[[Generic_Finalizer]]};  
  
  {'extensions', ptr --[[void*]]};

  {'internal', ptr --[[Face_Internal]]};
}

GlyphRec = alien.defstruct{
  {'library', ptr --[[Library]]};
  {'clazz', ptr --[[const Glyph_Class*]]}; -- private
  {'format', Glyph_Format};
  {'advance_x', Long};
  {'advance_y', Long};
}

BitmapGlyphRec = alien.defstruct{
  {'root_library', ptr --[[Library]]};
  {'root_clazz', ptr --[[const Glyph_Class*]]}; -- private
  {'root_format', Glyph_Format};
  {'root_advance_x', Long};
  {'root_advance_y', Long};
  {'left', Int};
  {'top', Int};
  {'bitmap', ptr --[[Bitmap]]};
}

OutlineGlyphRec = alien.defstruct{
  {'root_library', ptr --[[Library]]};
  {'root_clazz', ptr --[[const Glyph_Class*]]}; -- private
  {'root_format', Glyph_Format};
  {'root_advance_x', Long};
  {'root_advance_y', Long};
  {'outline_n_contours', Short};
  {'outline_n_points', Short};
  {'outline_points', ptr --[[Vector*]]};
  {'outline_tags', ptr --[[char*]]}; -- *not* a null terminated string
  {'outline_contours', ptr --[[short*]]};
  {'outline_flags', Int};
}

GlyphSlotRec = alien.defstruct{
  {"library", ptr --[[Library]]};
  {"face", ptr --[[Face]]};
  {"next", ptr --[[GlyphSlot]]};
  {"reserved", UInt};
  {"generic_data", ptr --[[void*]]};
  {"generic_finalizer", "callback" --[[Generic_Finalizer]]};
  {"metrics_width", Pos};
  {"metrics_height", Pos};
  {"metrics_horiBearingX", Pos};
  {"metrics_horiBearingY", Pos};
  {"metrics_horiAdvance", Pos};
  {"metrics_vertBearingX", Pos};
  {"metrics_vertBearingY", Pos};
  {"metrics_vertAdvance", Pos};
  {"linearHoriAdvance", Fixed};
  {"linearVertAdvance", Fixed};
  {"advance_x", Pos};
  {"advance_y", Pos};
  {"format", Glyph_Format};
  {"bitmap_rows", "int"};
  {"bitmap_width", "int"};
  {"bitmap_pitch", "int"};
  {"bitmap_buffer", "pointer" --[[unsigned char*]]};
  {"bitmap_num_grays", "short"};
  {"bitmap_pixel_mode", "char"};
  {"bitmap_palette_mode", "char"};
  {"bitmap_palette", "pointer" --[[void*]]};
  {"bitmap_left", Int};
  {"bitmap_top", Int};
  
  --[[
  Outline        outline;

  UInt           num_subglyphs;
  SubGlyph       subglyphs;

  void*             control_data;
  long              control_len;

  Pos            lsb_delta;
  Pos            rsb_delta;

  void*             other;

  Slot_Internal  internal;
  --]]
}

-- Functions

Init_FreeType = lib.FT_Init_FreeType
Init_FreeType:types{ ret=Error; ptr --[[Library]] }

Done_FreeType = lib.FT_Done_FreeType
Done_FreeType:types{ ret=Error; ptr --[[Library]] }

Open_Face = lib.FT_Open_Face
Open_Face:types{ ret=Error;
  ptr --[[Library]], ptr --[[Open_Args*]], Long --[[face_index]], ptr --[[Face*]] }

New_Face = lib.FT_New_Face
New_Face:types{ ret=Error;
  ptr --[[Library]], String --[[filepathname]], Long --[[face_index]], ptr --[[Face*]] }

Done_Face = lib.FT_Done_Face
Done_Face:types{ ret=Error; ptr --[[Face]] }

Set_Char_Size = lib.FT_Set_Char_Size
Set_Char_Size:types{ ret=Error;
  ptr --[[Face]], F26Dot6 --[[char_width]], F26Dot6 --[[char_height]], UInt --[[horz_resolution]], UInt --[[vert_resolution]] }

Load_Glyph = lib.FT_Load_Glyph
Load_Glyph:types{ ret=Error; ptr --[[Face]], UInt --[[glyph_index]], Int --[[load_flags]] }

Get_Glyph = lib.FT_Get_Glyph
Get_Glyph:types{ ret=Error; ptr --[[GlyphSlot]], ptr --[[Glyph*]] }

Glyph_To_Bitmap = lib.FT_Glyph_To_Bitmap
Glyph_To_Bitmap:types{ ret=Error; ptr --[[Glyph*]], Render_Mode, ptr --[[Vector* origin]], Bool --[[destroy]] }

Set_Pixel_Sizes = lib.FT_Set_Pixel_Sizes
Set_Pixel_Sizes:types{ ret=Error; ptr --[[Face]], UInt --[[pixel_width]], UInt --[[pixel_height]] }

Get_Char_Index = lib.FT_Get_Char_Index
Get_Char_Index:types{ ret=Error; ptr --[[Face]], ULong --[[charcode]] }

Render_Glyph = lib.FT_Render_Glyph
Render_Glyph:types{ ret=Error; ptr --[[GlyphSlot]], Render_Mode }

-- Error messages

errors = {
  [0x00] = "no error";
  [0x01] = "cannot open resource";
  [0x02] = "unknown file format";
  [0x03] = "broken file";
  [0x04] = "invalid FreeType version";
  [0x05] = "module version is too low";
  [0x06] = "invalid argument";
  [0x07] = "unimplemented feature";
  [0x08] = "broken table";
  [0x09] = "broken offset within table";
  [0x0A] = "array allocation size too large";

  -- glyph/character errors
  [0x10] = "invalid glyph index";
  [0x11] = "invalid character code";
  [0x12] = "unsupported glyph image format";
  [0x13] = "cannot render this glyph format";
  [0x14] = "invalid outline";
  [0x15] = "invalid composite glyph";
  [0x16] = "too many hints";
  [0x17] = "invalid pixel size";

  -- handle errors
  [0x20] = "invalid object handle";
  [0x21] = "invalid library handle";
  [0x22] = "invalid module handle";
  [0x23] = "invalid face handle";
  [0x24] = "invalid size handle";
  [0x25] = "invalid glyph slot handle";
  [0x26] = "invalid charmap handle";
  [0x27] = "invalid cache manager handle";
  [0x28] = "invalid stream handle";

  -- driver errors
  [0x30] = "too many modules";
  [0x31] = "too many extensions";

  -- memory errors
  [0x40] = "out of memory";
  [0x41] = "unlisted object";

  -- stream errors
  [0x51] = "cannot open stream";
  [0x52] = "invalid stream seek";
  [0x53] = "invalid stream skip";
  [0x54] = "invalid stream read";
  [0x55] = "invalid stream operation";
  [0x56] = "invalid frame operation";
  [0x57] = "nested frame access";
  [0x58] = "invalid frame read";

  -- raster errors
  [0x60] = "raster uninitialized";
  [0x61] = "raster corrupted";
  [0x62] = "raster overflow";
  [0x63] = "negative height while rastering";

  -- cache errors
  [0x70] = "too many registered caches";

  -- TrueType and SFNT errors */
  [0x80] = "invalid opcode";
  [0x81] = "too few arguments";
  [0x82] = "stack overflow";
  [0x83] = "code overflow";
  [0x84] = "bad argument";
  [0x85] = "division by zero";
  [0x86] = "invalid reference";
  [0x87] = "found debug opcode";
  [0x88] = "found ENDF opcode in execution stream";
  [0x89] = "nested DEFS";
  [0x8A] = "invalid code range";
  [0x8B] = "execution context too long";
  [0x8C] = "too many function definitions";
  [0x8D] = "too many instruction definitions";
  [0x8E] = "SFNT font table missing";
  [0x8F] = "horizontal header (hhea) table missing";
  [0x90] = "locations (loca) table missing";
  [0x91] = "name table missing";
  [0x92] = "character map (cmap) table missing";
  [0x93] = "horizontal metrics (hmtx) table missing";
  [0x94] = "PostScript (post) table missing";
  [0x95] = "invalid horizontal metrics";
  [0x96] = "invalid character map (cmap) format";
  [0x97] = "invalid ppem value";
  [0x98] = "invalid vertical metrics";
  [0x99] = "could not find context";
  [0x9A] = "invalid PostScript (post) table format";
  [0x9B] = "invalid PostScript (post) table";

  -- CFF, CID, and Type 1 errors */
  [0xA0] = "opcode syntax error";
  [0xA1] = "argument stack underflow";
  [0xA2] = "ignore";

  -- BDF errors
  [0xB0] = "'STARTFONT' field missing";
  [0xB1] = "'FONT' field missing";
  [0xB2] = "'SIZE' field missing";
  [0xB3] = "'CHARS' field missing";
  [0xB4] = "'STARTCHAR' field missing";
  [0xB5] = "'ENCODING' field missing";
  [0xB6] = "'BBX' field missing";
  [0xB7] = "'BBX' too big";
  [0xB8] = "Font header corrupted or missing fields";
  [0xB9] = "Font glyphs corrupted or missing fields";
}

function assert(v)
  if (v ~= 0) then
    error(errors[v], 2)
  end
end
